/* HttpClient Utils of smartwork frame work by lms 2016.06.21
 * first, you need to new HttpTool
 * then you can getHttpClient to obtain the httpClient
 * you need to dispose HttpTool when you finish your work
 * last update by lms 2021.02.27
 * */

package ms.core.tool;

import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;

import javax.net.ssl.SSLContext;

import org.apache.http.HttpEntity;
import org.apache.http.NameValuePair;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.conn.ssl.SSLConnectionSocketFactory;
import org.apache.http.conn.ssl.TrustStrategy;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.entity.mime.MultipartEntityBuilder;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.ssl.SSLContextBuilder;
import org.apache.http.util.EntityUtils;

public class HttpTool {

	public static class FileParam {
		private String paramName;
		private String fileName;

		public FileParam(String paramName, String fileName) {
			this.paramName = paramName;
			this.fileName = fileName;
		}

		public String getParamName() { return paramName; }
		public void setParamName(String paramName) { this.paramName = paramName; }
		
		public String getFileName() { return fileName; }
		public void setFileName(String fileName) { this.fileName = fileName; }
	}

	private int readTimeout 	= 0;
	private int connectTimeout 	= 0;
	private String charset		= "UTF-8";
	private int responseCode 	= 0;

	private CloseableHttpClient httpClient = null;
	private Map<String, String> header = new HashMap<String, String>();

	public void setTimeout(int connectTimeout, int readTimeout){
		this.connectTimeout = connectTimeout;
		this.readTimeout = readTimeout;
	}

	/**
	 * add http request header
	 * @param header key=value,such as: Accept-Encoding=UTF-8
	 */
	public HttpTool addHeader(String key, String value){
		this.header.put(key, value);
		return this;
	}
	public Map<String, String> getHeader(){ return this.header; }

	public String getCharset() { return charset; }
	public void setCharset(String charset) { this.charset = charset; }
	
	public int getResponseCode(){ return this.responseCode; }
	
	//constructor function to initialize HttpClient
	public HttpTool(){
		httpClient = HttpClients.createDefault();
	}
	
	public HttpTool(boolean redirect){
		if (!redirect) httpClient = HttpClients.custom().disableRedirectHandling().build();
		else httpClient = HttpClients.custom().setRedirectStrategy(new LaxRedirectStrategy()).build();
	}

	public HttpTool(boolean https, boolean redirect) {
		if (!https) {
			if (redirect) httpClient = HttpClients.custom().setRedirectStrategy(new LaxRedirectStrategy()).build();
			else httpClient = HttpClients.custom().disableRedirectHandling().build();
		}
		else {
			if (redirect)
				httpClient = HttpClients.custom().setRedirectStrategy(new LaxRedirectStrategy()).setSSLSocketFactory(createSSLConnSocketFactory()).build();
			else
				httpClient = HttpClients.custom().disableRedirectHandling().setSSLSocketFactory(createSSLConnSocketFactory()).build();							
		}
	}

	//to close CloseableHttpClient
	public void dispose(){
		closeHttpClient();
	}

	//to get HttpClient Object
	public CloseableHttpClient getHttpClient(){
		return this.httpClient;
	}

	private void updateRequestHeader(HttpRequestBase http) {
		for (Entry<String, String> entry : header.entrySet()) {
			http.addHeader(entry.getKey(), entry.getValue());
		}
	}
	
	private void setRequestTimeout(HttpRequestBase http) {
		if (connectTimeout>0 && readTimeout>0){
			RequestConfig requestConfig = RequestConfig.custom()
					.setConnectTimeout(connectTimeout)
					.setSocketTimeout(readTimeout)
					.build();
			http.setConfig(requestConfig);
		}
	}

	//execute url to do http request and return content
	public String get(String url){
		String ret   = "";

		try {
			HttpGet get = new HttpGet(url);
			updateRequestHeader(get);
			setRequestTimeout(get);

			CloseableHttpResponse httpResponse = httpClient.execute(get);
			if (httpResponse==null){
				get.abort();
				return null;
			}

			this.responseCode = httpResponse.getStatusLine().getStatusCode();
			HttpEntity entity = httpResponse.getEntity();
			if (entity!=null){
				ret = EntityUtils.toString(entity, charset);
			}
			closeHttpResponse(httpResponse);
		}catch (IOException e) {
			Log4j2Tool.error(HttpTool.class, "get:", e);
		}
		return ret;
	}
	
	/**
	 * POST 方法执行 url
	 * @param url 请求地址
	 * @param data 提交数据
	 * @param encode 提交数据编码
	*/
	public String post(String url, Map<String, String> data, String encode){
		String ret = "";
	
		try {
			HttpPost post = new HttpPost(url);
			//设置http头
			updateRequestHeader(post);
			//设置超时
			setRequestTimeout(post);

			//设置提交数据
			List<NameValuePair> list = new ArrayList<NameValuePair>();
			for (Entry<String, String> entry : data.entrySet()) {
				list.add(new BasicNameValuePair(entry.getKey(), entry.getValue()));
			}
			UrlEncodedFormEntity uefEntity = new UrlEncodedFormEntity(list, encode);
            post.setEntity(uefEntity);

            CloseableHttpResponse httpResponse = httpClient.execute(post);
            if (httpResponse==null){
            	post.abort();
                return null;
            }

			this.responseCode = httpResponse.getStatusLine().getStatusCode();
            HttpEntity entity = httpResponse.getEntity();
            if (entity!=null){
                ret = EntityUtils.toString(entity, charset);
            }
            closeHttpResponse(httpResponse);
        } catch (Exception e){
        	Log4j2Tool.error(HttpTool.class, "post:", e);
        }
		return ret;
	}

	/**
	 * POST 方法执行 url
	 * @param url 请求地址
	 * @param data 提交数据
	 * @param encode 提交数据编码
	*/
	public String post(String url,String data,String encode){
		String ret = "";
				
		try {
			HttpPost post = new HttpPost(url);
			//设置http头
			updateRequestHeader(post);
			//设置超时
			setRequestTimeout(post);

			StringEntity strEntity = new StringEntity(data,encode);
            post.setEntity(strEntity);
            CloseableHttpResponse httpResponse = httpClient.execute(post);
            if (httpResponse==null){
            	post.abort();
                return null;
            }

			responseCode = httpResponse.getStatusLine().getStatusCode();
            HttpEntity entity = httpResponse.getEntity();
            if (entity!=null){
                ret = EntityUtils.toString(entity,encode);
            }
            closeHttpResponse(httpResponse);
        } catch (Exception e){
        	Log4j2Tool.error(HttpTool.class, "post:", e);
        }
		return ret;
	}

	public String uploadFile(String url, List<FileParam> files, Map<String, String> body) {
		Map<String, String> ret = new HashMap<>();
		if (files==null || files.size()==0) {
			ret.put("result", "failure");
			ret.put("code", "S102");
			ret.put("msg", "no attachments found.");

			return JsonTool.objectToJson(ret);
		}

		try {
			HttpPost post = new HttpPost(url);
			updateRequestHeader(post);
			
			MultipartEntityBuilder builder = MultipartEntityBuilder
					.create()
					.setCharset(StandardCharsets.UTF_8);
			// 设置提交参数
			if (body!=null) {
				for(Map.Entry<String, String> item: body.entrySet()) {
					// ContentType.TEXT_PLAIN.withCharset("UTF-8") 可解决中文乱码问题
					builder.addTextBody(item.getKey(), item.getValue(), ContentType.TEXT_PLAIN.withCharset("UTF-8"));
				}
			}
			// 设置提交的附件
			for(FileParam param: files) {
				File file = new File(param.getFileName());
				builder.addBinaryBody(param.paramName, file, ContentType.DEFAULT_BINARY, file.getName());
			}

	        // 构建请求实体
	        post.setEntity(builder.build());
	        // 执行请求
            CloseableHttpResponse response = httpClient.execute(post);
            // 获取响应实体
            String resp = EntityUtils.toString(response.getEntity());
            // 关闭响应
            response.close();

			return resp;
		} catch(Exception e) {
			ret.put("result", "failure");
			ret.put("code", "S001");
			ret.put("msg", e.getMessage());

			return JsonTool.objectToJson(ret);
		}
	}

	//to close httpClient object
	private void closeHttpClient(){
		if (httpClient==null)
			return;

		try {
			httpClient.close();
			httpClient = null;
		} catch (IOException e) {
			Log4j2Tool.error(HttpTool.class, "closeHttpClient:", e);
		}
	}
	
	//to close httpResponse
	private void closeHttpResponse(CloseableHttpResponse httpResponse){
		if (httpResponse==null)
			return;

		try {
			httpResponse.close();
		} catch (IOException e) {
			Log4j2Tool.error(HttpTool.class, "closeHttpResponse:", e);
		}
	}

	/**
     * 创建SSL安全连接 
     * 
     * @return
     */
	private SSLConnectionSocketFactory createSSLConnSocketFactory(){
		try{
			SSLContext sslContext = new SSLContextBuilder().loadTrustMaterial(null, new TrustStrategy(){
				public boolean isTrusted(X509Certificate[] chain,String authType) throws CertificateException {
					return true;
				}
			}).build();

			SSLConnectionSocketFactory csf = new SSLConnectionSocketFactory(sslContext);

			return csf;
        } catch (Exception e) {
        	Log4j2Tool.error(HttpTool.class, "createSSLConnSocketFactory:", e);
            return null;
        }
    }

	public static void main(String[] args){
		try {
			HttpTool http = new HttpTool(true);
			http.addHeader("user-agent","Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36 Edg/89.0.774.57")
				.addHeader("accept-language","zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6")
				.addHeader("accept","text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9")
				.addHeader("accept-encoding","gzip, deflate, br");
			System.out.println(http.get("https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=wxe308e4b5cb78ff33&secret=e89cbcd44b0c9f3817d486318a4dfb2f"));
			http.dispose();
		}catch(Exception e) {
			e.printStackTrace();
		}
	}
}
